package de.unikoblenz.west.csxpoi.server;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;

/**
 * Encapsulates categories, which are identified by their URI and have a name,
 * superclasses, subclasses, and equivalents.
 */
public class CategoryWrapper {

	/**
	 * the URI of the category
	 */
	private String mUri = null;

	/**
	 * the name of the category
	 */
	private String mName = null;

	/**
	 * the list of superclasses of the category
	 */
	private List<CategoryWrapper> mSuperclasses = new ArrayList<CategoryWrapper>();

	/**
	 * the list of subclasses of the category
	 */
	private List<CategoryWrapper> mSubclasses = new ArrayList<CategoryWrapper>();

	/**
	 * the list of equivalents of the category
	 */
	private List<CategoryWrapper> mEquivalents = new ArrayList<CategoryWrapper>();

	/**
	 * Constructs a new category with the specified URI and name.
	 * 
	 * @param uri
	 *            the URI of the category
	 * @param name
	 *            the name of the category
	 */
	public CategoryWrapper(String uri, String name) {
		mUri = uri;
		mName = name;
	}

	/**
	 * Creates a category from the specified URI by retrieving all properties
	 * from the repository.
	 * 
	 * @param uri
	 *            the URI of the category to create
	 * @param connection
	 *            a connection to the repository
	 * @return a category with the specified URI and the retrieved properties or
	 *         null if the creation failed
	 * @throws RepositoryException
	 * @throws MalformedQueryException
	 * @throws QueryEvaluationException
	 * @throws NumberFormatException
	 */
	public static CategoryWrapper createFromUri(String uri,
			RepositoryConnection connection) throws RepositoryException,
			MalformedQueryException, QueryEvaluationException,
			NumberFormatException {

		CategoryWrapper category = null;

		// check for valid URI
		try {
			ValueFactory valueFactory = connection.getValueFactory();
			URI uriUri = valueFactory.createURI(uri);
			uri = uriUri.getNamespace()
					+ URLEncoder.encode(uriUri.getLocalName(), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			return null;
		}

		// Retrieve URI and name:
		String request = "PREFIX base: <" + Constants.NS_BASE + "> "
				+ "PREFIX owl: <" + Constants.NS_OWL + "> " + "PREFIX rdfs: <"
				+ Constants.NS_RDFS + "> " + "SELECT ?category ?name WHERE {"
				+ " ?category rdfs:subClassOf base:Poi ;"
				+ "  rdfs:label ?name ." + " OPTIONAL {"
				+ "  ?removal a base:CategoryRemoval ;"
				+ "   base:affectsCategory ?category ." + " }"
				+ " FILTER (!bound(?removal))" + " FILTER (?category = <" + uri
				+ ">)" + "} LIMIT 1";

		TupleQuery query = connection.prepareTupleQuery(QueryLanguage.SPARQL,
				request);
		TupleQueryResult result = query.evaluate();

		try {
			if (result.hasNext()) {
				BindingSet bindingSet = result.next();
				category = new CategoryWrapper(bindingSet.getValue("category")
						.stringValue(), bindingSet.getValue("name")
						.stringValue());
			}
		} finally {
			result.close();
		}

		if (category != null) {

			// Retrieve subclasses:
			TupleQuery subclassesQuery = connection.prepareTupleQuery(
					QueryLanguage.SPARQL, "PREFIX base: <" + Constants.NS_BASE
							+ "> " + "PREFIX rdfs: <" + Constants.NS_RDFS
							+ "> " + "SELECT ?subclass ?name WHERE {"
							+ " ?subclass rdfs:subClassOf ?category ;"
							+ "  rdfs:subClassOf base:Poi ;"
							+ "  rdfs:label ?name ."
							+ " FILTER (?subclass != ?category)"
							+ " FILTER (?category = <" + uri + ">)" + "}");
			TupleQueryResult subclassesResult = subclassesQuery.evaluate();

			try {
				while (subclassesResult.hasNext()) {
					BindingSet bindingSet = subclassesResult.next();
					CategoryWrapper subclass = new CategoryWrapper(bindingSet
							.getValue("subclass").stringValue(), bindingSet
							.getValue("name").stringValue());
					category.addSubclass(subclass);
				}
			} finally {
				subclassesResult.close();
			}

			// Retrieve superclasses:
			TupleQuery superclassesQuery = connection.prepareTupleQuery(
					QueryLanguage.SPARQL, "PREFIX base: <" + Constants.NS_BASE
							+ "> " + "PREFIX rdfs: <" + Constants.NS_RDFS
							+ "> " + "SELECT ?superclass ?name WHERE {"
							+ " ?category rdfs:subClassOf ?superclass ."
							+ " ?superclass rdfs:subClassOf base:Poi ;"
							+ "  rdfs:label ?name ."
							+ " FILTER (?superclass != ?category)"
							+ " FILTER (?category = <" + uri + ">)" + "}");
			TupleQueryResult superclassesResult = superclassesQuery.evaluate();
			try {
				while (superclassesResult.hasNext()) {
					BindingSet bindingSet = superclassesResult.next();
					CategoryWrapper superclass = new CategoryWrapper(bindingSet
							.getValue("superclass").stringValue(), bindingSet
							.getValue("name").stringValue());
					category.addSuperclass(superclass);
				}
			} finally {
				superclassesResult.close();
			}

			// Retrieve equivalents:
			TupleQuery equivalentsQuery = connection.prepareTupleQuery(
					QueryLanguage.SPARQL, "PREFIX owl: <" + Constants.NS_OWL
							+ "> " + "PREFIX rdfs: <" + Constants.NS_RDFS
							+ "> " + "SELECT ?equivalent ?name WHERE {"
							+ " ?category owl:sameAs ?equivalent ."
							+ " ?equivalent rdfs:label ?name ."
							+ " FILTER (?equivalent != ?category)"
							+ " FILTER (?category = <" + uri + ">)" + "}");
			TupleQueryResult equivalentsResult = equivalentsQuery.evaluate();
			try {
				while (equivalentsResult.hasNext()) {
					BindingSet bindingSet = equivalentsResult.next();
					CategoryWrapper equivalent = new CategoryWrapper(bindingSet
							.getValue("equivalent").stringValue(), bindingSet
							.getValue("name").stringValue());
					category.addEquivalent(equivalent);
				}
			} finally {
				equivalentsResult.close();
			}

			// Retrieve modifications:
			TupleQuery modificationsQuery = connection
					.prepareTupleQuery(
							QueryLanguage.SPARQL,
							"PREFIX base: <"
									+ Constants.NS_BASE
									+ "> "
									+ "PREFIX rdfs: <"
									+ Constants.NS_RDFS
									+ "> "
									+ "SELECT ?category ?changes ?adds ?deletes ?value ?name ?cname WHERE {"
									+ " ?modification a base:CategoryModification ;"
									+ "  base:affectsCategory ?category ;"
									+ "  base:issuedAt ?issuedAt ."
									+ " OPTIONAL { ?modification base:changesCategoryProperty ?changes }"
									+ " OPTIONAL { ?modification base:addsCategoryProperty ?adds }"
									+ " OPTIONAL { ?modification base:deletesCategoryProperty ?deletes }"
									+ " OPTIONAL { ?modification base:categoryPropertyValue ?value }"
									+ " OPTIONAL { ?value rdfs:label ?name }"
									+ " OPTIONAL { ?category rdfs:label ?cname }"
									+ " FILTER (?category = <" + uri + ">"
									+ "  || (bound(?value) && ?value = <" + uri
									+ ">))" + "} ORDER BY ASC(?issuedAt)");
			TupleQueryResult modificationsResult = modificationsQuery
					.evaluate();
			try {
				while (modificationsResult.hasNext()) {
					try {
						BindingSet bindingSet = modificationsResult.next();
						if (bindingSet.hasBinding("changes")) {
							category.setName(bindingSet.getValue("value")
									.stringValue());
						} else if (bindingSet.hasBinding("adds")) {
							if (bindingSet.getValue("adds").stringValue()
									.equals(Constants.URI_RDFS_SUB_CLASS_OF)) {
								if (bindingSet.getValue("category")
										.stringValue().equals(uri)) {
									CategoryWrapper superclass = new CategoryWrapper(
											bindingSet.getValue("value")
													.stringValue(), bindingSet
													.getValue("name")
													.stringValue());
									category.addSuperclass(superclass);
								} else {
									CategoryWrapper subclass = new CategoryWrapper(
											bindingSet.getValue("category")
													.stringValue(), bindingSet
													.getValue("cname")
													.stringValue());
									category.addSubclass(subclass);
								}
							} else if (bindingSet.getValue("adds")
									.stringValue()
									.equals(Constants.URI_OWL_SAME_AS)) {
								if (bindingSet.getValue("category")
										.stringValue().equals(uri)) {
									CategoryWrapper equivalent = new CategoryWrapper(
											bindingSet.getValue("value")
													.stringValue(), bindingSet
													.getValue("name")
													.stringValue());
									category.addEquivalent(equivalent);
								} else {
									CategoryWrapper equivalent = new CategoryWrapper(
											bindingSet.getValue("category")
													.stringValue(), bindingSet
													.getValue("cname")
													.stringValue());
									category.addEquivalent(equivalent);
								}
							}
						} else if (bindingSet.hasBinding("deletes")) {
							if (bindingSet.getValue("deletes").stringValue()
									.equals(Constants.URI_RDFS_SUB_CLASS_OF)) {
								if (bindingSet.getValue("category")
										.stringValue().equals(uri)) {
									CategoryWrapper superclass = new CategoryWrapper(
											bindingSet.getValue("value")
													.stringValue(), bindingSet
													.getValue("name")
													.stringValue());
									category.deleteSuperclass(superclass);
								} else {
									CategoryWrapper subclass = new CategoryWrapper(
											bindingSet.getValue("category")
													.stringValue(), bindingSet
													.getValue("cname")
													.stringValue());
									category.deleteSubclass(subclass);
								}
							} else if (bindingSet.getValue("deletes")
									.stringValue()
									.equals(Constants.URI_OWL_SAME_AS)) {
								if (bindingSet.getValue("category")
										.stringValue().equals(uri)) {
									CategoryWrapper equivalent = new CategoryWrapper(
											bindingSet.getValue("value")
													.stringValue(), bindingSet
													.getValue("name")
													.stringValue());
									category.deleteEquivalent(equivalent);
								} else {
									CategoryWrapper equivalent = new CategoryWrapper(
											bindingSet.getValue("catagory")
													.stringValue(), bindingSet
													.getValue("cname")
													.stringValue());
									category.deleteEquivalent(equivalent);
								}
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			} finally {
				modificationsResult.close();
			}
		}
		return category;
	}

	/**
	 * Sets the name of the category.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		mName = name;
	}

	/**
	 * Gets the URI of the category.
	 * 
	 * @return the URI of the category
	 */
	public String getUri() {
		return mUri;
	}

	/**
	 * Gets the name of the category.
	 * 
	 * @return the name of the category
	 */
	public String getName() {
		return mName;
	}

	/**
	 * Gets the superclasses of the category.
	 * 
	 * @return a copy of the list of superclasses of the category
	 */
	public List<CategoryWrapper> getSuperclasses() {
		return new ArrayList<CategoryWrapper>(mSuperclasses);
	}

	/**
	 * Gets the subclasses of the category.
	 * 
	 * @return a copy of the list of subclasses of the category
	 */
	public List<CategoryWrapper> getSubclasses() {
		return new ArrayList<CategoryWrapper>(mSubclasses);
	}

	/**
	 * Gets the equivalents of the category.
	 * 
	 * @return a copy of the list of equivalents of the category
	 */
	public List<CategoryWrapper> getEquivalents() {
		return new ArrayList<CategoryWrapper>(mEquivalents);
	}

	/**
	 * Adds a superclass to the category. It is added to the list of
	 * superclasses unless it is already present.
	 * 
	 * @param superclass
	 *            the superclass to add
	 */
	public void addSuperclass(CategoryWrapper superclass) {
		if (!mSuperclasses.contains(superclass))
			mSuperclasses.add(superclass);
	}

	/**
	 * Adds a superclass to the category, which is created from its URI. If the
	 * creation fails, no superclass is added.
	 * 
	 * @param uri
	 *            the uri of the superclass
	 * @param connection
	 *            a connection to the repository
	 * @throws RepositoryException
	 * @throws MalformedQueryException
	 * @throws QueryEvaluationException
	 */
	public void addSuperclass(String uri, RepositoryConnection connection)
			throws RepositoryException, MalformedQueryException,
			QueryEvaluationException {
		CategoryWrapper category = CategoryWrapper.createFromUri(uri,
				connection);
		if (category != null)
			addSuperclass(category);
	}

	/**
	 * Adds a subclass to the category. It is added to the list of subclasses
	 * unless it is already present.
	 * 
	 * @param subclass
	 *            the subclass to add
	 */
	public void addSubclass(CategoryWrapper subclass) {
		if (!mSubclasses.contains(subclass))
			mSubclasses.add(subclass);
	}

	/**
	 * Adds a subclass to the category, which is created from its URI. If the
	 * creation fails, no subclass is added.
	 * 
	 * @param uri
	 *            the uri of the subclass
	 * @param connection
	 *            a connection to the repository
	 * @throws RepositoryException
	 * @throws MalformedQueryException
	 * @throws QueryEvaluationException
	 */
	public void addSubclass(String uri, RepositoryConnection connection)
			throws RepositoryException, MalformedQueryException,
			QueryEvaluationException {
		CategoryWrapper category = CategoryWrapper.createFromUri(uri,
				connection);
		if (category != null)
			addSubclass(category);
	}

	/**
	 * Adds an equivalent to the category. It is added to the list of
	 * equivalents unless it is already present.
	 * 
	 * @param equivalent
	 *            the equivalent to add
	 */
	public void addEquivalent(CategoryWrapper equivalent) {
		if (!mEquivalents.contains(equivalent))
			mEquivalents.add(equivalent);
	}

	/**
	 * Adds an equivalent to the category, which is created from its URI. If the
	 * creation fails, no equivalent is added.
	 * 
	 * @param uri
	 *            the uri of the equivalent
	 * @param connection
	 *            a connection to the repository
	 * @throws RepositoryException
	 * @throws MalformedQueryException
	 * @throws QueryEvaluationException
	 */
	public void addEquivalent(String uri, RepositoryConnection connection)
			throws RepositoryException, MalformedQueryException,
			QueryEvaluationException {
		CategoryWrapper category = CategoryWrapper.createFromUri(uri,
				connection);
		if (category != null)
			addEquivalent(category);
	}

	/**
	 * Deletes a superclass of the category. It is removed from the list of
	 * superclasses.
	 * 
	 * @param superclass
	 *            the superclass to delete
	 */
	public void deleteSuperclass(CategoryWrapper superclass) {
		mSuperclasses.remove(superclass);
	}

	/**
	 * Deletes a subclass of the category. It is removed from the list of
	 * subclasses.
	 * 
	 * @param subclass
	 *            the subclass to delete
	 */
	public void deleteSubclass(CategoryWrapper subclass) {
		mSubclasses.remove(subclass);
	}

	/**
	 * Deletes an equivalent of the category. It is removed from the list of
	 * equivalents.
	 * 
	 * @param equivalent
	 *            the equivalent to delete
	 */
	public void deleteEquivalent(CategoryWrapper equivalent) {
		mEquivalents.remove(equivalent);
	}

	/**
	 * Categories are equal if their URIs match.
	 */
	@Override
	public boolean equals(Object category) {
		try {
			return mUri.equals(((CategoryWrapper) category).getUri());
		} catch (ClassCastException e) {
			return false;
		} catch (NullPointerException e) {
			return false;
		}
	}

}
